/************************************************************************
**
**  Copyright (C) 2009, 2010, 2011  Strahinja Markovic  <strahinja.markovic@gmail.com>
**
**  This file is part of Sigil.
**
**  Sigil is free software: you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, either version 3 of the License, or
**  (at your option) any later version.
**
**  Sigil is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with Sigil.  If not, see <http://www.gnu.org/licenses/>.
**
*************************************************************************/

#include <stdafx.h>
#include "HTMLResource.h"
#include "BookManipulation/XercesCppUse.h"
#include "Misc/Utility.h"
#include "BookManipulation/CleanSource.h"
#include "BookManipulation/XhtmlDoc.h"
#include "BookManipulation/GuideSemantics.h"
#include <QStringBuilder>   // reimplements the '%' operator
#include "CSSResource.h"	// used in embedded-fonts workaround

static const QString LOADED_CONTENT_MIMETYPE = "application/xhtml+xml";
const QString XML_NAMESPACE_CRUFT = "xmlns=\"http://www.w3.org/1999/xhtml\"";
const QString REPLACE_SPANS = "<span class=\"SigilReplace_\\d*\"( id=\"SigilReplace_\\d*\")*>";

const QString XML_TAG = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>";

HTMLResource::HTMLResource( const QString &fullfilepath, 
                            const QHash< QString, Resource* > &resources,
                            QObject *parent )
    : 
    Resource( fullfilepath, parent ),
    m_WebPage( NULL ),
    m_TextDocument( NULL ),
    m_WebPageModified( false ),
    m_WebPageIsOld( true ),
    m_TextDocumentIsOld( true ),
    c_jQuery(         Utility::ReadUnicodeTextFile( ":/javascript/jquery-1.6.2.min.js"          ) ),
    c_jQueryScrollTo( Utility::ReadUnicodeTextFile( ":/javascript/jquery.scrollTo-1.4.2-min.js" ) ),
    c_jQueryWrapSelection( Utility::ReadUnicodeTextFile( ":/javascript/jquery.wrapSelection.js" ) ),
    m_Resources( resources ),
    m_ResourceCount( 0 )
{

}


Resource::ResourceType HTMLResource::Type() const
{
    return Resource::HTMLResourceType;
}


QWebPage& HTMLResource::GetWebPage()
{
    Q_ASSERT( m_WebPage );

    return *m_WebPage;
}


QTextDocument& HTMLResource::GetTextDocument()
{
    Q_ASSERT( m_TextDocument );

    return *m_TextDocument;
}


void HTMLResource::SetDomDocument( shared_ptr< xc::DOMDocument > document )
{
    QWriteLocker locker( &GetLock() );

    m_DomDocument = document;
    MarkSecondaryCachesAsOld();
}


const xc::DOMDocument& HTMLResource::GetDomDocumentForReading()
{
    return *m_DomDocument;
}


xc::DOMDocument& HTMLResource::GetDomDocumentForWriting()
{
    // We can't just mark the caches as old right here since
    // some consumers need write access but don't end up writing anything.

    return *m_DomDocument;
}


void HTMLResource::MarkSecondaryCachesAsOld()
{
    m_WebPageIsOld      = true;
    m_TextDocumentIsOld = true;
}

void HTMLResource::UpdateDomDocumentFromWebPage()
{
    if ( !WebPageModified() )

        return;

    Q_ASSERT( QThread::currentThread() == QApplication::instance()->thread() );

    m_DomDocument = XhtmlDoc::LoadTextIntoDocument( GetWebPageHTML() );

    m_TextDocumentIsOld = true;
    SetWebPageModified( false );
}


void HTMLResource::UpdateDomDocumentFromTextDocument()
{
    if ( !TextDocumentModified() )

        return;

    Q_ASSERT( QThread::currentThread() == QApplication::instance()->thread() );
    Q_ASSERT( m_TextDocument );

    m_DomDocument = XhtmlDoc::LoadTextIntoDocument( ConvertToEntities( CleanSource::Clean( m_TextDocument->toPlainText() ) ) );

    m_WebPageIsOld = true;
    SetTextDocumentModified( false );
}


void HTMLResource::UpdateWebPageFromDomDocument()
{
    Q_ASSERT( QThread::currentThread() == QApplication::instance()->thread() );

    // We will not receive a signal, if a resource is added, so the
    // resource count is tracked internally
    if ( m_Resources.count() != m_ResourceCount )
    {
        m_RefreshNeeded = true;
        m_ResourceCount = m_Resources.count();
    }

    if ( !m_WebPageIsOld && !m_RefreshNeeded )

        return;

    if ( m_WebPage == NULL )
    {
        m_WebPage = new QWebPage( this );
        connect( m_WebPage, SIGNAL( contentsChanged() ), this, SLOT( SetWebPageModified() ) );
    }

    //SetWebPageHTML( XhtmlDoc::GetDomDocumentAsString( *m_DomDocument ) );
    // Before passing the Document to the WebPage, a stylesheet override
    // is applied. The overide is a workaround for the buggy support of
    // @font-face in QtWebKit
    QStringList paths_to_linked_resources;
    SetWebPageHTML( GetDomDocWithStyleOverride( *m_DomDocument,  paths_to_linked_resources ) );
    paths_to_linked_resources.append( GetPathsToImgFromDOMDocument( ) );

    TrackNewResources( paths_to_linked_resources );

    SetWebPageModified( false );
    m_WebPageIsOld = false;
    m_RefreshNeeded = false;
}


void HTMLResource::UpdateTextDocumentFromDomDocument()
{
    Q_ASSERT( QThread::currentThread() == QApplication::instance()->thread() );

    if ( !m_TextDocumentIsOld )

        return;

    if ( m_TextDocument == NULL )
    {
        m_TextDocument = new QTextDocument( this );
        m_TextDocument->setDocumentLayout( new QPlainTextDocumentLayout( m_TextDocument ) );
    }

    m_TextDocument->setPlainText( ConvertToEntities( CleanSource::PrettyPrint( XhtmlDoc::GetDomDocumentAsString( *m_DomDocument ) ) ) );

    m_TextDocument->setModified( false );
    m_TextDocumentIsOld = false;
}


void HTMLResource::SaveToDisk( bool book_wide_save )
{
    {
        QWriteLocker locker( &GetLock() );

        Utility::WriteUnicodeTextFile( ConvertToEntities( CleanSource::PrettyPrint( XhtmlDoc::GetDomDocumentAsString( *m_DomDocument ) ) ),
                                       GetFullPath() );
    }

    if ( !book_wide_save )

        emit ResourceUpdatedOnDisk();
}


void HTMLResource::RemoveWebkitCruft()
{
    Q_ASSERT( m_WebPage );

    QWebElementCollection collection = m_WebPage->mainFrame()->findAllElements( ".Apple-style-span" );

    foreach( QWebElement element, collection )
    {
        element.toggleClass( "Apple-style-span" );
    }

    collection = m_WebPage->mainFrame()->findAllElements( ".webkit-indent-blockquote" );

    foreach( QWebElement element, collection )
    {
        element.toggleClass( "webkit-indent-blockquote" );
    }

    QWebElement body_tag =  m_WebPage->mainFrame()->findFirstElement( "body" );

    // Removing junk webkit styles
    body_tag.setStyleProperty( "word-wrap", "" );
    body_tag.setStyleProperty( "-webkit-nbsp-mode", "" );
    body_tag.setStyleProperty( "-webkit-line-break", "" );

    // Banish the irritating <body style=""> tags
    if( body_tag.attribute( "style", "none" ) == "" )
    {
        body_tag.removeAttribute( "style" );
    }
}

QString HTMLResource::RemoveBookViewReplaceSpans( const QString &source )
{
    QRegExp replace_spans( REPLACE_SPANS );
    replace_spans.setMinimal( true );
    QRegExp span_open_or_close( "<\\s*(/)*\\s*span\\s*>");
    span_open_or_close.setMinimal( true );

    QString newsource = "";
    int left_pos = 0;
    int index = source.indexOf( replace_spans );
    while( index != -1 )
    {
        // Append the text between the last capture and this one.
        newsource.append( source.mid( left_pos, index - left_pos ) );

        // Advance past the captured opening tag.
        index += replace_spans.cap(0).length();
        left_pos = index;

        // Check for nested spans.
        int nest_count = 1; // set to 1 as we already have an open span
        int next_span_tag = index;
        do 
        {
            next_span_tag = source.indexOf( span_open_or_close, index );
            if( next_span_tag == -1 )
            {
                // Content is not well-formed, which should never happen here.
                boost_throw( ErrorParsingXml()
                             << errinfo_XML_parsing_error_string( "GetWebPageHTML() has returned invalid xhtml" ) );
            }

            if( !span_open_or_close.cap(0).contains( "/" ) )
            {
                // Opening tag, so increment the counter.
                nest_count++;
            }
            else
            {
                // Closing tag, so decrement the counter
                nest_count--;
            }
        } while( nest_count > 0 );

        // next_span_tag now points to the start of the closing tag of the span we're removing.
        // Append the source from the end of the span tag to the start of the closing tag
        newsource.append( source.mid( index, next_span_tag - index ) );

        // Move left_pos past the closing tag and search for another span to remove.
        left_pos = next_span_tag + span_open_or_close.cap(0).length(); 
        index = source.indexOf( replace_spans, left_pos );
    }

    // Append the rest of the source after all the spans have been removed.
    newsource.append( source.mid( left_pos ) );

    // It's possible that we might have replace spans nested within each other,
    // so go back to the start and check again, and recurse if found.
    if( newsource.indexOf( replace_spans ) != -1 )
    {
        newsource = RemoveBookViewReplaceSpans( newsource );
    }

    return newsource;
}

QStringList HTMLResource::SplitOnSGFChapterMarkers()
{
    QStringList chapters = XhtmlDoc::GetSGFChapterSplits( XhtmlDoc::GetDomDocumentAsString( *m_DomDocument ) );

    m_DomDocument = XhtmlDoc::LoadTextIntoDocument( CleanSource::Clean( chapters.takeFirst() ) );
    MarkSecondaryCachesAsOld();

    return chapters;
}


void HTMLResource::LinkedResourceUpdated()
{
    m_RefreshNeeded = true;

    QWebSettings::clearMemoryCaches();
}


// We need to load the jQuery libs here since
// we sometimes need to use them outside of
// BookViewEditor.
void HTMLResource::WebPageJavascriptOnLoad()
{
    Q_ASSERT( m_WebPage );

    m_WebPage->mainFrame()->evaluateJavaScript( c_jQuery         );
    m_WebPage->mainFrame()->evaluateJavaScript( c_jQueryScrollTo );
    m_WebPage->mainFrame()->evaluateJavaScript( c_jQueryWrapSelection );
}


void HTMLResource::SetWebPageModified( bool modified )
{
    m_WebPageModified = modified;
}


// QWebPage::isModified() only looks at form data,
// which makes it absolutely useless.
bool HTMLResource::WebPageModified()
{
    return m_WebPageModified;
}


// We have a "modified" getter and setter for
// the TextDocument even when it has its own
// because QWebPage does not and we want a unified interface.
void HTMLResource::SetTextDocumentModified( bool modified )
{
    m_TextDocument->setModified( modified );
}


bool HTMLResource::TextDocumentModified()
{
    return m_TextDocument->isModified();
}


QString HTMLResource::GetWebPageHTML()
{
    Q_ASSERT( m_WebPage );

    RemoveWebkitCruft();

    // Set the xml tag here rather than let Tidy do it.
    // This prevents false mismatches with the cache later on.
    // QString html_from_Qt = m_WebPage->mainFrame()->toHtml();
    
    // Remove the @font-face override - has to be done before tidy cleans the stylesheet
    QString html_from_Qt = GetWebPageHtmlRestoreOverride( m_WebPage );

    html_from_Qt = RemoveBookViewReplaceSpans( html_from_Qt );
    html_from_Qt = html_from_Qt.remove( XML_NAMESPACE_CRUFT );
    return ConvertToEntities( CleanSource::PrettyPrint( CleanSource::Clean( XML_TAG % html_from_Qt ) ) );
}


void HTMLResource::SetWebPageHTML( const QString &source )
{
    Q_ASSERT( m_WebPage );

    connect( m_WebPage, SIGNAL( loadFinished( bool ) ), this, SLOT( WebPageJavascriptOnLoad() ) );

    // NOTE: content loading is asynchronous, and attempts to read the content back out immediately
    // with toHtml() *will* fail if the page contains external links, particularly if those cannot
    // be resolved and it ends up waiting for a timeout. The web page content is only readable
    // once it issues the loadFinished() signal.
    m_WebPage->mainFrame()->setContent( source.toUtf8(), LOADED_CONTENT_MIMETYPE, GetBaseUrl() );

    m_WebPage->setContentEditable( true );

    // TODO: we kill external links; a dialog should be used
    // that asks the user if he wants to open this external link in a browser
    m_WebPage->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );

    // Track resources whose change will necessitate an update of the WebView.
    // At present this only applies to css files and images.
    // This happens now in UpdateWebPageFromDomDocument because here all
    // links to stylesheets are removed
    // TrackNewResources( GetPathsToLinkedResources() );

    QWebSettings &settings = *m_WebPage->settings();
    settings.setAttribute( QWebSettings::LocalContentCanAccessRemoteUrls, false );
    settings.setAttribute( QWebSettings::JavascriptCanAccessClipboard, true );
    settings.setAttribute( QWebSettings::ZoomTextOnly, true );
}


QStringList HTMLResource::GetPathsToLinkedResources()
{
    Q_ASSERT( m_WebPage );

    QWebElementCollection elements = m_WebPage->mainFrame()->findAllElements( "link, img" );

    QStringList linked_resources;

    foreach( QWebElement element, elements )
    {
        // We skip the link elements that are not stylesheets
        if ( element.tagName().toLower() == "link" && 
             element.attribute( "rel" ).toLower() != "stylesheet" )
        {
            continue;
        }

        if ( element.hasAttribute( "href" ) )
        
            linked_resources.append( element.attribute( "href" ) );        

        else if ( element.hasAttribute( "src" ) )
        
            linked_resources.append( element.attribute( "src" ) );
    }

    return linked_resources;
}


void HTMLResource::TrackNewResources( const QStringList &filepaths )
{    
    foreach( QString resource_id, m_LinkedResourceIDs )
    {
        Resource *resource = m_Resources.value( resource_id );

        if ( resource )
        {
            disconnect( resource, SIGNAL( ResourceUpdatedOnDisk() ),    this, SLOT( LinkedResourceUpdated() ) );
            disconnect( resource, SIGNAL( Deleted( const Resource& ) ), this, SLOT( LinkedResourceUpdated() ) );
        }
    }

    m_LinkedResourceIDs.clear();
    QStringList filenames;

    foreach( QString filepath, filepaths )
    {
        filenames.append( QFileInfo( filepath ).fileName() );
    }

    foreach( Resource *resource, m_Resources.values() )
    {
        if ( filenames.contains( resource->Filename() ) )
            
            m_LinkedResourceIDs.append( resource->GetIdentifier() );
    }

    foreach( QString resource_id, m_LinkedResourceIDs )
    {
        Resource *resource = m_Resources.value( resource_id );

        if ( resource )
        {
            connect( resource, SIGNAL( ResourceUpdatedOnDisk() ),    this, SLOT( LinkedResourceUpdated() ) );
            connect( resource, SIGNAL( Deleted( const Resource& ) ), this, SLOT( LinkedResourceUpdated() ) );
        }
    }
}


QString HTMLResource::ConvertToEntities( const QString &source )
{
    QString newsource = source;

    newsource = newsource.replace( QString::fromUtf8( "\u00ad" ), "&shy;" );
    newsource = newsource.replace( QString::fromUtf8( "\u2014" ), "&mdash;" );
    newsource = newsource.replace( QString::fromUtf8( "\u2013" ), "&ndash;" );

    return newsource;
}

QStringList HTMLResource::GetPathsToImgFromDOMDocument( )
{
    QStringList result;

    xc::DOMNodeList *list = m_DomDocument->getElementsByTagName(QtoX("img"));
    for( uint i = 0; i < list->getLength(); ++i )
    {
        xc::DOMElement *img = dynamic_cast<xc::DOMElement*>(list->item(i));

        if (img->hasAttribute( QtoX( "href" ) ) )
            result.append( XtoQ( img->getAttribute( QtoX( "href") ) ) );
        else if (img->hasAttribute( QtoX("url") ) )
            result.append( XtoQ( img->getAttribute( QtoX( "url") ) ) );
    }

    return result;
}

QString HTMLResource::GetHeadFromDOMDocument()
{
    xc::DOMElement *head = dynamic_cast<xc::DOMElement*>(m_DomDocument->getElementsByTagName( QtoX( "head" ) )->item(0) );
    QString dom_head = XhtmlDoc::GetDomNodeAsString( dynamic_cast<xc::DOMNode&>(*head) );
    // remove namespace from <head> tag
    dom_head.remove( " " % XML_NAMESPACE_CRUFT );

    return dom_head;
}

QString HTMLResource::GetWebPageHtmlRestoreOverride( const QWebPage* page )
{
    QString source = page->mainFrame()->toHtml();

    // Get the original head section from the dom document
    xc::DOMElement *head = dynamic_cast<xc::DOMElement*>(m_DomDocument->getElementsByTagName( QtoX( "head" ) )->item(0) );
    QString dom_head = XhtmlDoc::GetDomNodeAsString( dynamic_cast<xc::DOMNode&>(*head) );
    // remove namespace from <head> tag
    dom_head.remove( " " % XML_NAMESPACE_CRUFT );

    // Get the complete head section from WebPage
    QWebElement page_head = page->mainFrame()->documentElement().findFirst( "head" );
    QString web_head = page_head.toOuterXml();
    web_head.remove( " " % XML_NAMESPACE_CRUFT );

    // Restore unmodified head-section
    // The full-text replacement is used because there is no safe way
    // to find html tags with RegExp
    if ( !dom_head.isEmpty() && !web_head.isEmpty() )
        source.replace( web_head, dom_head );

    // Check if override is really removed. The full-text replacement will
    // fail, if XML_NAMESPACE_CRUFT does not match the documents namespace
    if ( source.contains( "@@SigilOverrideStylesheet@@" ) )
        QMessageBox::critical( 
        0,
        tr( "Sigil" ),
        tr( "The stylesheet override could not be removed."
        "You found a bug, possibly caused by unusual namespace declarations in your html.\n\n"
        "Do NOT save the document, or your stylesheet will be messed.")
        );

    return source;
}

QString HTMLResource::GetDomDocWithStyleOverride( const xc::DOMDocument &doc, QStringList &linked_resources )
{
    QString merged_stylesheet;

    // Refresh the internal resource map
    RefreshFontResources();

    // I don't want to mess with the actual document, so a temp clone is created
    xc::DOMDocument *doc_clone = dynamic_cast<xc::DOMDocument*>(doc.cloneNode( true ) );

    // Get a copy of the current head node from DOM-Document
    xc::DOMElement *head = dynamic_cast<xc::DOMElement*>(doc_clone->getElementsByTagName( QtoX( "head" ) )->item(0) );
    if ( !head )
    {
        return XhtmlDoc::GetDomDocumentAsString( doc );
    }

    // Merge all <link>'ed stylesheets
    xc::DOMNodeList *list = head->getElementsByTagName( QtoX( "link" ) );
    for( uint i = 0; i < list->getLength(); ++i )
    {
        xc::DOMElement *link = dynamic_cast<xc::DOMElement*>( list->item(i) );

         // We skip the link elements that are not stylesheets
         if ( XtoQ( link->getAttribute( QtoX( "rel") ) ).toLower() == "stylesheet" )
         {
             // Remove the link element
             head->removeChild( link );

             QString path;
             if ( link->hasAttribute( QtoX("href") ) )
                path = XtoQ( link->getAttribute( QtoX("href") ) );
             else if ( link->hasAttribute( QtoX("url") ) )
                path = XtoQ( link->getAttribute( QtoX("url") ) );

             link->release();

             merged_stylesheet.append(
                 MergeImportedStylesheets( GetStylesheetFromResource( path ),
                 linked_resources ) );
             linked_resources.append( path );
         }
    }

    // Add all local stylesheets to the merged stylesheet
    // By appending local stylesheets after all linked stylesheets
    // the cascading order is preserved
    list = head->getElementsByTagName( QtoX( "style" ) );
    for( uint i = 0; i < list->getLength(); ++i )
    {
        xc::DOMElement *link = dynamic_cast<xc::DOMElement*>( list->item(i) );
        if ( XtoQ( link->getAttribute( QtoX ( "type" ) ) ).toLower() == "text/css" )
        {
            // There could be @import rules, so we pass the stylesheet to the merge function
            merged_stylesheet.append(
                MergeImportedStylesheets( XtoQ (link->getTextContent()), linked_resources ) );
            head->removeChild( link )->release();
        }
    }

    // Remove all @namespace rules
    merged_stylesheet = RemoveCSSNamespaceRules( merged_stylesheet );

    // Override all @fontface rules
    QStringList used_fonts = OverrideAllFontFaceRules( merged_stylesheet );

    // Add used font.resources to resource tracking
    // We add the filename instead of the path, TrackNewResources
    // will remove the path anyway
    linked_resources.append( used_fonts );

    // Unload all unused fonts
    // Without these step, the application fonts will be used in the webpage
    // even there is no according @font-face rule
    foreach( QString font_file, m_FontResources.keys() ) 
    {
        if (!used_fonts.contains( font_file ) )
            m_FontResources[ font_file ]->UnloadFont();
    }

    // Add a tag, to check removal of override
    merged_stylesheet = "/*@@SigilOverrideStylesheet@@*/\n" % merged_stylesheet;

    xc::DOMElement* new_style = doc_clone->createElementNS( head->getNamespaceURI(), QtoX("style") );
    new_style->setAttribute( QtoX("type"), QtoX("text/css") );
    new_style->setTextContent( QtoX( merged_stylesheet ) );
    
    head->appendChild( new_style );

    // Get the modified head section as html
    QString source = XhtmlDoc::GetDomDocumentAsString( *doc_clone );

    doc_clone->release();

    return source;
}

/* All imported stylesheets are parsed every time when switching to bok view
 * There should be some kind of cache, maybe in the css-resouce
 */
QString HTMLResource::MergeImportedStylesheets( const QString &stylesheet, QStringList &linked_resources )
{
    if ( stylesheet.isEmpty() )
        return "";

    QString merged_stylesheet = stylesheet;

    // RegExp is not the first choice for parsing stylesheets
    // There might be some constructs where this function will 
    // not recognize what is valid css.
    // 
    // OPS 2.0.1 says that strings (url's) MUST be quoted, but ADE
    // seems to accept unquoted strings and so do we
    // Maybe check the quotes in flightcrew?

    // First remove all comments, so we don't find invalid rules
    // ignores comment tags in ".." '..' or (..) 
    QRegExp rx( "(?![\\(\"'])/\\*[^\\*]+\\*/(?![\\)\"'])" );
    rx.setMinimal(true);
    rx.setCaseSensitivity( Qt::CaseInsensitive );
    merged_stylesheet.remove(rx).trimmed();

    // If @charset is found, it is simply removed (which could cause
    // problems, if it is really used)
    // Maybe prompt the user in this case?
    rx.setPattern("^[\\W]*@charset\\s*([^'\"\\n]*|\"[^\"]*\"|'[^']*')*;");
    merged_stylesheet.remove(rx);

    // Iterate through imported stylesheets
    // The @import rule must be defined before any other rule
    // (except @charset) according to W3C
    QString pattern = 
        "^\\s*"  // only match at begin of search region (see CaretAtOffest)
        "(?=@import)\\s*" // taken from flightcrew - ReachabilityAnalysis.cpp
        "[^;\\}\\(\"']*"
        "(?:"
        "url\\([\"']?([^\\)\"']+)[\"']?\\)"
        "|"
        "[\"']([^\"']+)[\"']"
        ")"
        "[^;\\}]*"
        "(?:;|\\})";
    rx.setPattern( pattern );

    int pos = 0;
    while (pos >= 0) {
        pos = rx.indexIn( merged_stylesheet, pos, QRegExp::CaretAtOffset );
        if (pos >= 0) {
            QString path = rx.cap(1);    // save path
            QString imported_stylesheet = MergeImportedStylesheets( GetStylesheetFromResource( path ), linked_resources );
            merged_stylesheet.replace(pos, rx.matchedLength(), imported_stylesheet );      // delete import
            linked_resources.append( path );
            pos += imported_stylesheet.length();    
        }
    }

    return merged_stylesheet;
}

QString HTMLResource::GetStylesheetFromResource( const QString &path )
{
    QString filename = QFileInfo( path ).fileName().toLower();
    QString stylesheet;
    foreach( Resource *resource, m_Resources.values() )
    {
        // Search all resources for matching filename
        if ( resource->Type() == Resource::CSSResourceType && resource->Filename().toLower() == filename )
        {
            stylesheet = ((CSSResource*)resource)->GetText();
            break;
        }
    }

    return stylesheet;
}

QString HTMLResource::RemoveCSSNamespaceRules( const QString &stylesheet )
{
    QString new_stylesheet = stylesheet;

    QRegExp rx( "@namespace([^'\"]*|\"[^\"]*\"|'[^']*')*;" );
    rx.setMinimal( true );
    rx.setCaseSensitivity( Qt::CaseInsensitive );
    new_stylesheet.remove( rx );

    return new_stylesheet;

}

QStringList HTMLResource::OverrideAllFontFaceRules( QString &stylesheet )
{
    QStringList font_resources;
    QRegExp rx( "@font-face\\s*\\{([^'\"]*|\"[^\"]*\"|'[^']*')*\\}" );
    rx.setMinimal( true );
    rx.setCaseSensitivity( Qt::CaseInsensitive );

    int pos = 0;
    while (pos >= 0) {
        pos = rx.indexIn(stylesheet, pos);
        if (pos >= 0) {
            QString rule = rx.cap();
            QString font_resource = OverrideFontFaceRule( rule );
            if ( !font_resource.isEmpty()) font_resources.append( font_resource );
            stylesheet.replace( pos, rx.matchedLength(), rule );
            pos += rule.length();
        }
    }

    return font_resources;
}

QString HTMLResource::OverrideFontFaceRule( QString &rule)
{
    QString filename;
    
    filename = GetFontFilenameFromRule(rule);
    if ( !filename.isEmpty() )
    {
        // This is the actual workaround to support @font-face
        // Since all font are loaded to the QFontDatabase, we can
        // use them as local fonts
        QRegExp rx ("src:.*;");
        rx.setMinimal(true);
        rule.replace( rx, "" );
        rule.replace("}", "src: local(\"" % m_FontResources[filename]->GetFontFamily() % "\"); }");
        m_FontResources[filename]->LoadFont();
    }
    else
    {
        // referenced font-file is not embedded - use WebKits default SansSerifFont
        QRegExp rx( "src:.*;" );
        rx.setMinimal( true );
        rule.replace( rx, "" );
        rule.replace( "}", "src: local('" % m_WebPage->settings()->fontFamily( QWebSettings::SansSerifFont ) % "'); }" );
    }

    return filename;
}

QString HTMLResource::GetFontFilenameFromRule(const QString &rule)
{
    QString filename;
    QString tmp_name;
    QRegExp rx("url\\([\"']?([^\\)\"']+)[\"']?\\)");
    rx.setMinimal(true);
    int pos=0;
    while (pos >= 0) {
        // There could be multiple url attributes - always use the last one
        pos=rx.indexIn(rule, pos);
        if (pos >= 0) {
            tmp_name = QFileInfo( rx.cap(1) ).fileName();	// First subexpression is the path
            if (m_FontResources.contains( tmp_name ) ) {
                filename = tmp_name;
            }
            pos += rx.matchedLength();
        }
    }

    return filename;
}

void HTMLResource::RefreshFontResources()
{
    if ( !m_RefreshNeeded )
        return;

    m_FontResources.clear();

    // Create the internal list of font resources
    foreach( Resource *resource, m_Resources.values() )
    {
        if ( resource->Type() == Resource::FontResourceType ) {
            m_FontResources.insert(resource->Filename(), (FontResource*)resource);
        }
    }
}
